SSH 模块注册与异步注册核心逻辑
模块架构设计
SSH 模块采用双层架构:
SshModule(外层,固定接口)
└── SshCoreModule(内层,核心实现)
├── forRoot() 同步注册
├── forRootAsync() 异步注册
└── SshService SSH 服务
text
外层模块只做接口透传,不包含任何业务逻辑。这样设计的好处是:
- 外层 API 稳定不变,内部实现可以自由重构
- 核心逻辑集中在 Core Module 中,职责清晰
类型定义
在 ssh.interfaces.ts 中定义所有类型:
import { ConnectConfig } from 'ssh2'
import { ModuleMetadata } from '@nestjs/common'
// 同步注册的选项 —— 直接继承 ssh2 的连接配置
export interface SshModuleOptions extends ConnectConfig {}
// 异步注册的选项
export interface SshModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
useExisting?: any
useClass?: any
useFactory?: (...args: any[]) => Promise<SshModuleOptions> | SshModuleOptions
inject?: any[]
}
// 工厂接口
export interface SshOptionsFactory {
createSshOptions(): Promise<SshModuleOptions> | SshModuleOptions
}
typescript
这里直接继承了 ssh2 包中 ConnectConfig 的类型定义,无需手动重复定义 host、port、username、password、privateKey 等字段。安装 @types/ssh2 后即可使用:
pnpm add -D @types/ssh2
bash
ConnectConfig 包含了 SSH 连接所需的全部配置项:
interface ConnectConfig {
host?: string
port?: number
username?: string
password?: string
privateKey?: Buffer | string
// ... 更多选项
}
typescript
外层模块实现
ssh.module.ts 负责接口固定和透传:
import { DynamicModule, Module } from '@nestjs/common'
import { SshCoreModule } from './ssh-core.module'
@Module({})
export class SshModule {
static forRoot(options: SshModuleOptions): DynamicModule {
return SshCoreModule.forRoot(options)
}
static forRootAsync(options: SshModuleAsyncOptions): DynamicModule {
return SshCoreModule.forRootAsync(options)
}
}
typescript
核心模块实现
ssh-core.module.ts 包含所有注册逻辑:
import { DynamicModule, Global, Module, Provider } from '@nestjs/common'
export const SSH_OPTIONS = 'SSH_OPTIONS'
@Global()
@Module({})
export class SshCoreModule {
// 同步注册
static forRoot(options: SshModuleOptions): DynamicModule {
const sshOptionsProvider: Provider = {
provide: SSH_OPTIONS,
useValue: options,
}
return {
module: SshCoreModule,
providers: [sshOptionsProvider, SshService],
exports: [SshService],
}
}
// 异步注册
static forRootAsync(options: SshModuleAsyncOptions): DynamicModule {
const asyncProviders = this.createAsyncProviders(options)
return {
module: SshCoreModule,
providers: [...asyncProviders, SshService],
exports: [SshService],
}
}
}
typescript
同步注册流程
forRoot(options)
└── 创建 Provider: { provide: 'SSH_OPTIONS', useValue: options }
└── 注册 SshService
└── 导出 SshService
text
异步注册流程
异步注册支持三种方式:useFactory、useClass、useExisting。
private static createAsyncProviders(
options: SshModuleAsyncOptions
): Provider[] {
// 如果使用 useFactory
if (options.useFactory) {
return [
{
provide: SSH_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
},
]
}
// 如果使用 useClass 或 useExisting
return [this.createAsyncOptionsProvider(options)]
}
private static createAsyncOptionsProvider(
options: SshModuleAsyncOptions
): Provider {
if (options.useFactory) {
return {
provide: SSH_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
}
}
// useClass 模式
if (options.useClass) {
return {
provide: SSH_OPTIONS,
useFactory: async (optionsFactory: SshOptionsFactory) => {
return await optionsFactory.createSshOptions()
},
inject: [options.useClass],
}
}
// useExisting 模式
throw new Error('Invalid async options')
}
typescript
全局模块说明
核心模块标记了 @Global() 装饰器,这意味着:
SshService注册为全局可用 Provider- 其他模块无需显式导入
SshModule即可注入SshService - 这是 NestJS 官方模块的通用做法(如
ConfigModule、ScheduleModule)
在 AppModule 中使用
// 同步注册
@Module({
imports: [
SshModule.forRoot({
host: '192.168.31.77',
port: 22,
username: 'root',
password: 'your_password',
}),
],
})
export class AppModule {}
// 异步注册(通过 ConfigService 读取配置)
@Module({
imports: [
SshModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
host: config.get('SSH_HOST'),
port: config.get('SSH_PORT'),
username: config.get('SSH_USERNAME'),
password: config.get('SSH_PASSWORD'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
typescript
小结
- SSH 模块采用双层层架构:外层固定接口,内层实现逻辑
- 类型定义直接继承
ssh2的ConnectConfig,避免重复定义 - 同步注册(
forRoot)直接传入连接配置 - 异步注册(
forRootAsync)支持useFactory、useClass、useExisting三种模式 - 核心模块标记
@Global(),使SshService在全局可用 - 异步注册的 Provider 创建逻辑是固定写法,与 NestJS 官方模块保持一致
↑